<?php
/**
 ***********************************************************************************************************************
 * csrf解决方案 可用于表单重复验证
 * @author llq
 ***********************************************************************************************************************
 */
namespace artisan\secure;
use artisan\cache;
use artisan\config;
use artisan\cookie;

/**
 * Class csrf
 * @package artisan\secure
 */
class csrf
{
    /**
     * redis storage key prefix
     */
    const STORAGE_PREFIX = 'csrf:token:';

    /**
     * @var string|null
     */
    private static $session_id = null;

    /**
     * 生成全局token 依赖cookie存储session id
     *
     * @param int $expire token有效期
     * @param int $times token有效次数
     * @return bool|string
     */
    public static function createToken($times = 1, $expire = 3600)
    {
        $token = self::createId(self::getTokenSalt());
        $key = self::STORAGE_PREFIX . md5(self::getSid() . '~' . $token);
        return self::getRedis()->set($key, $times, $expire) ? $token : false;
    }

    /**
     * 验证令牌
     *
     * @param $token
     * @return bool
     */
    public static function verifyToken($token)
    {
        $key = self::STORAGE_PREFIX . md5(self::getSid() . '~' . $token);
        if(!empty($ret = self::getRedis()->get($key))) {
            self::getRedis()->decr($key);
            return true;
        }
        return false;
    }

    /**
     * 创建加盐唯一ID
     *
     * @param string $salt
     * @return string
     */
    private static function createId($salt)
    {
        return md5(hash('haval128,5', uniqid(microtime(true) . rand())) . (string)$salt, false);
    }

    /**
     * @return null|string
     */
    private static function getSid()
    {
        if(
            empty(self::$session_id) &&
            empty(self::$session_id = cookie::get(self::getCookieName()))
        ) {
            self::$session_id = self::createId(self::getSessionSalt());
            cookie::set(
                self::getCookieName(),
                self::$session_id,
                self::getCookieExpire()
            );
        }
        return self::$session_id;
    }

    /**
     * 获取csrf存储在cookie的session_id键值
     *
     * @return string
     */
    private static function getCookieName()
    {
        return (string)(config::get('global', 'csrf.cookie.name') ?: 'KB_CSRF');
    }

    /**
     * 获取生成session id时所使用的干扰salt
     *
     * @return string
     */
    private static function getSessionSalt()
    {
        return (string)(config::get('global', 'csrf.session.salt') ?: 'KB_CSRF_SESSION_SALT');
    }

    /**
     * 存储session id的cookie的有效期 默认30天
     *
     * @return int
     */
    private static function getCookieExpire()
    {
        return (int)(config::get('global', 'csrf.cookie.expire') ?: 86400*30);
    }

    /**
     * 获取生成csrf token时所使用的干扰salt
     *
     * @return string
     */
    private static function getTokenSalt()
    {
        return (string)(config::get('global', 'csrf.token.salt') ?: 'KB_CSRF_TOKEN_SALT');
    }

    /**
     * @var cache\PHPredisDriver
     */
    private static $redis;

    /**
     * @return cache\PHPredisDriver
     */
    private static function getRedis()
    {
        if(empty(self::$redis)) {
            self::$redis = cache::connect(config::get('global', 'csrf.redis') ?: '');
        }
        return self::$redis;
    }
}